#include <windows.h>
#include <wininet.h>
#include <iostream>
#include <string>
#include <sstream>
#include <format>
#include <string_view>
#include <memory>
#include <print>
#include <fstream>

using namespace std;

#define ERR(fun, code) ErrorMessage(__FILE__, __LINE__, __FUNCSIG__, fun, code)
#define E(fun) throw ERR(fun, GetLastError())
string ErrorMessage(string_view filename, unsigned int line, string_view parent_function, string_view function, unsigned long dwCode); // 输出错误信息

void PrintNetErr();

class Url
{
public:
    Url(string_view url) : m_hOpen(nullptr), m_hUrl(nullptr)
    {
        m_hOpen = InternetOpenW(nullptr, INTERNET_OPEN_TYPE_DIRECT, nullptr, nullptr, 0);
        if(!m_hOpen)
            E("InternetOpenW");
        m_hUrl = InternetOpenUrlA(m_hOpen, url.data(), NULL, 0, 0, 0);
        if(!m_hUrl)
        {
            DWORD dwErrCode = GetLastError();
            PrintNetErr();
            throw ERR("InternetOpenUrlA", dwErrCode);
        }
    }
    ~Url()
    {
        if(m_hUrl) InternetCloseHandle(m_hUrl);
        if(m_hOpen) InternetCloseHandle(m_hOpen);
    }
    Url(const Url&) = delete;
    Url(Url &&o) : m_hOpen(o.m_hOpen), m_hUrl(o.m_hUrl)
    {
        o.m_hOpen = nullptr;
        o.m_hUrl = nullptr;
    }
    Url& operator=(const Url&) = delete;

private:
    HINTERNET m_hOpen;
    HINTERNET m_hUrl;
    friend ostream &operator<<(ostream &out, Url &&url);
};

ostream &operator<<(ostream &out, Url &&url)
{
    DWORD dwSize = 0;
    do
    {
        if (!InternetQueryDataAvailable(url.m_hUrl, &dwSize, 0, 0))
            E("InternetQueryDataAvailable");
        else
        {
            unique_ptr<char[]> buf(new char[dwSize]);
            DWORD dwRead;
            if (!InternetReadFile(url.m_hUrl, buf.get(), dwSize, &dwRead))
            {
                E("InternetReadFile");
                dwSize = 0;
            }
            else
            {
                out.write(buf.get(), dwRead);
            }
        }
    } while (dwSize);
    return out;
}

int main(int argc, char **argv)
{
    if (argc < 2 || argc > 3)
    {
        println(stderr, "usage: {} url [output filepath]", argv[0]);
        return 1;
    }

    try
    {
        if (argc == 2)
            cout << Url(argv[1]) << endl;
        else
        {
            ofstream file(argv[2]);
            file << Url(argv[1]);
        }
    }
    catch(const string& s)
    {
        println(stderr, "{}", s);
    }
    
}

void PrintNetErr()
{
    DWORD dwLen = 0;
    DWORD dwError = 0;
    InternetGetLastResponseInfoA(NULL, NULL, &dwLen);
    unique_ptr<char[]> buf(new char[dwLen + 1]);
    InternetGetLastResponseInfoA(&dwError, buf.get(), &dwLen);
    println(stderr, "error: {}, buffer: {}", dwError, buf.get());
}

string ErrorMessage(string_view filename, unsigned int line, string_view parent_function, string_view function, unsigned long dwCode) // 输出错误信息
{
    LPSTR lpMsgBuf = nullptr;
    FormatMessageA(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS |
            FORMAT_MESSAGE_MAX_WIDTH_MASK,
        NULL,
        dwCode,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPSTR)&lpMsgBuf,
        0, NULL);

    if (!lpMsgBuf)
        return "";
    unique_ptr<char, HLOCAL(WINAPI *)(HLOCAL)> _(lpMsgBuf, LocalFree);

    return format(
        "Filename: {}\n"
        "Line: {}\n"
        "Parent: {}\n"
        "Function: {}\n"
        "Code: {}\n"
        "Description: {}\n",
        filename, line, parent_function, function, dwCode, lpMsgBuf);
}